// Copyright (C) 2010 Razvan Constantin <razvan.constantin@rconst.net>
//
// This file is part of FileBow.
//
// FileBow is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// FileBow is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with FileBow.  If not, see <http://www.gnu.org/licenses/>.

// Implementation of geany wrapped methods.

#include <geanyplugin.h>
#include "ui/geany-bridge.h"

GeanyData* geany_data;
GeanyPlugin* geany_plugin;
GeanyFunctions* geany_functions;

void GB_document_open_file(const char* filename, int readonly) {
  document_open_file(filename, readonly, NULL, NULL);
}

void GB_msgwin_status_add(const char* msg) {
  msgwin_status_add(msg);
}

GtkWidget* GB_tools_menu() {
  return geany->main_widgets->tools_menu;
}

void GB_plugin_signal_connect(GObject* object, const char* signal,
                int after, GCallback callback,
                gpointer user_data) {
  plugin_signal_connect(geany_plugin, object, signal, after,
                        callback, user_data);
}

void smart_indent(guint key_id);
void insert_tab(guint key_id);
void GB_setup_keys(GtkWidget* menu) {
  GeanyKeyGroup* keyGroup;
  keyGroup = plugin_set_key_group(geany_plugin, "Navigate", /*items*/ 3, NULL);
  keybindings_set_item(keyGroup, /*key_id*/ 0, /*callback*/ NULL,
                       /*key*/ 0, (GdkModifierType) 0, (gchar*) "navigate_menu",
                       (gchar*) "File Navigate", menu);
  keybindings_set_item(keyGroup, /*key_id*/ 1, /*callback*/ smart_indent,
                       /*key*/ 0, (GdkModifierType) 0, (gchar*) "smart_indent",
                       (gchar*) "Smart Indent", (GtkWidget*) NULL);
  keybindings_set_item(keyGroup, /*key_id*/ 2, /*callback*/ insert_tab,
                       /*key*/ 0, (GdkModifierType) 0, (gchar*) "insert_tab",
                       (gchar*) "Insert Tab", (GtkWidget*) NULL);
}

const gchar* GB_geany_project_base_path() {
  if (!geany->app->project || !geany->app->project->base_path) {
    return NULL;
  }
  return geany->app->project->base_path;
}

const gchar* GB_current_document_path() {
  GeanyDocument* current = document_get_current();
  return current ? current->real_path : NULL;
}

gboolean GB_valid_document_idx(int i) {
}

const gchar* GB_document_next_valid_path(int* i) {
  for (; *i < GEANY(documents_array)->len; ++(*i)) {
  if (documents[*i]->is_valid && documents[*i]->real_path) {
    return documents[*i]->real_path;
  }
  }
  return NULL;
}

static void on_project_open(GObject *obj, GKeyFile *config, gpointer user_data) {
  filebow_on_project_open();
}
static void on_project_save(GObject *obj, GKeyFile *config, gpointer user_data) {
  filebow_on_project_save();
}
static void on_project_close(GObject *obj, gpointer user_data) {
  filebow_on_project_close();
}
static void on_document_open(GObject *obj, GeanyDocument *doc, gpointer user_data) {
  if (doc->is_valid && doc->real_path) {
    filebow_on_document_open(doc->real_path);
  }
}
static void on_document_activate(GObject *obj, GeanyDocument *doc, gpointer user_data) {
  if (doc->is_valid && doc->real_path) {
    filebow_on_document_activate(doc->real_path);
  }
}
static void on_document_close(GObject *obj, GeanyDocument *doc, gpointer user_data) {
  if (doc->is_valid && doc->real_path) {
    filebow_on_document_close(doc->real_path);
  }
}
PluginCallback plugin_callbacks[] = {
  { "project-open", (GCallback) &on_project_open, TRUE, NULL },
  { "project-save", (GCallback) &on_project_save, TRUE, NULL },
  { "project-close", (GCallback) &on_project_close, TRUE, NULL },
  { "document-open", (GCallback) &on_document_open, TRUE, NULL },
  { "document-activate", (GCallback) &on_document_activate, TRUE, NULL },
  { "document-close", (GCallback) &on_document_close, TRUE, NULL },
  { NULL, NULL, FALSE, NULL }
};

extern PLUGIN_VERSION_CHECK(147)
extern PLUGIN_SET_INFO((gchar*) "FileBow",
                       (gchar*) "Quick File Navigator",
                       (gchar*) "0.1",
                       (gchar*) "Razvan Constantin")

void filebow_plugin_init();
extern void plugin_init(GeanyData* data) {
  filebow_plugin_init();
}

void filebow_plugin_cleanup();
extern void plugin_cleanup(void) {
  filebow_plugin_cleanup();
}

// Indent consists in the exact whitespace prefix string plus desired increments
// in the number of indents.
typedef struct Indent {
  // Exact whitespace prefix of indentation reference line plus extra spaces.
  // Use g_free to release it.
  gchar* whitespace;
  // Increments in the number of indents after whitespaces prefix is set.
  gint extra_indents;
} Indent;

gchar* get_whitespace_prefix(ScintillaObject* sci, gint line) {
  gint begin = sci_get_position_from_line(sci, line);
  gint end = sci_get_line_end_position(sci, line);
  gint pos, ws_size;
  for (pos = begin, ws_size = 0; pos < end; ++pos, ++ws_size) {
    gchar ch = sci_get_char_at(sci, pos);
    if (ch != ' ' && ch != '\t') {
      break;
    }
  }
  return sci_get_contents_range(sci, begin, begin + ws_size);
}

gboolean make_indent(ScintillaObject* sci, gint line, gint extra_indents,
                     gint ws_size, Indent* indent) {
  if (ws_size == 0) {
    indent->whitespace = g_strdup("");
  } else if (ws_size < 0) {
    indent->whitespace = get_whitespace_prefix(sci, line);
  } else {
    gint begin = sci_get_position_from_line(sci, line);
    indent->whitespace = sci_get_contents_range(sci, begin, begin + ws_size);
    gint i;
    for (i = 0; i < ws_size; ++i) {
      if (indent->whitespace[i] != ' ' && indent->whitespace[i] != '\t') {
        indent->whitespace[i] = ' ';
      }
    }
  }
  indent->extra_indents = MAX(0, extra_indents);
  return TRUE;
}

gboolean get_smart_indent_after(ScintillaObject* sci, gint line,
                                Indent* indent) {
  gint current_line = line;
  gint begin = sci_get_position_from_line(sci, current_line);
  gint pos = sci_get_line_end_position(sci, current_line) - 1;
  gint parentheses = 0, square = 0, curly = 0;
  for (; pos >= begin; --pos) {
    gchar ch = sci_get_char_at(sci, pos);
    // TODO: For bracket chars ignore comments by style.
    switch (ch) {
      case '(':
        ++parentheses;
        if (parentheses > 0) {
          return make_indent(sci, current_line, curly, pos - begin + 1, indent);
        }
        break;
      case ')':
        --parentheses;
        break;
      case '[':
        ++square;
        if (square > 0) {
          return make_indent(sci, current_line, curly, pos - begin + 1, indent);
        }
        break;
      case ']':
        --square;
        break;
      case '{':
        ++curly;
        break;
      case '}':
        --curly;
        break;
    }
    if (pos == begin && (parentheses < 0 || square < 0)) {
      --current_line;
      if (current_line < 0) {
        return FALSE;
      }
      begin = sci_get_position_from_line(sci, current_line);
    }
  }
  return make_indent(sci, current_line, curly, -1, indent);
}

void smart_indent(guint key_id) {
  // Document.
  GeanyDocument* doc = document_get_current();
  if (!doc) {
    return;
  }
  // Scintilla object.
  ScintillaObject* sci = doc->editor->sci;
  if (!sci) {
    return;
  }
  // Current line.
  gint line = sci_get_current_line(sci);
  if (!line) {
    return;
  }
  // Get expected indent.
  Indent indent;
  if (!get_smart_indent_after(sci, line-1, &indent)) {
    return;
  }
  // Apply indent.
  gchar* whitespace = get_whitespace_prefix(sci, line);
  if (indent.extra_indents || strcmp(whitespace, indent.whitespace)) {
    // Save current position.
    gint current_pos = sci_get_current_position(sci);
    // Replace whitespace.
    gint begin = sci_get_position_from_line(sci, line);
    sci_set_selection_start(sci, begin);
    sci_set_selection_end(sci, begin + strlen(whitespace));
    sci_replace_sel(sci, indent.whitespace);
    if (indent.extra_indents) {
      const GeanyIndentPrefs *iprefs = editor_get_indent_prefs(doc->editor);
      gint indents = sci_get_line_indentation(sci, line);
      indents += iprefs->width * indent.extra_indents;
      sci_set_line_indentation(sci, line, indents);
    }
    // Restore selection.
    if (current_pos >= begin) {
      gchar* new_whitespace = get_whitespace_prefix(sci, line);
      current_pos += strlen(new_whitespace) - strlen(whitespace);
      g_free(new_whitespace);
    }
    sci_set_current_position(sci, current_pos, FALSE);
  }
  g_free(indent.whitespace);
  g_free(whitespace);
}

void insert_tab(guint key_id) {
  // Document.
  GeanyDocument* doc = document_get_current();
  if (!doc) {
    return;
  }
  // Scintilla object.
  ScintillaObject* sci = doc->editor->sci;
  if (!sci) {
    return;
  }
  // Save current position.
  gint current_pos = sci_get_current_position(sci);
  sci_insert_text(sci, current_pos, "\t");
}
